#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "spas_client.h"
#include "sha1.h"
#include "sha256.h"
#include "hmac.h"
#include "base64.h"

#ifdef WIN32
#include <io.h>  
#include <process.h>  
#else
#include <unistd.h>
#endif
#ifdef SPAS_MT
#include <pthread.h>
#endif

#ifdef __cplusplus
namespace metaqSignature{
#endif

#define SPAS_VERSION "SPAS_V1_0"

static SPAS_CREDENTIAL g_credential;
static char g_path[SPAS_MAX_PATH];
static int g_loaded = 0;
static unsigned int refresh = 10;
static time_t modified = 0;

#ifdef SPAS_MT

static pthread_mutex_t cred_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_once_t cred_once = PTHREAD_ONCE_INIT;
static pthread_key_t cred_key;

#endif

extern void * _mem_alloc(unsigned int size);
extern void * _mem_realloc(void *ptr, unsigned int old_size, unsigned int new_size);
extern void _mem_free(void *ptr);
extern void _trim(char *str);


void * _mem_alloc(unsigned int size) {
	void *p = malloc(size);
	if (p != NULL) {
		memset(p, 0, size);
	}
	return p;
}

void * _mem_realloc(void *ptr, unsigned int old_size, unsigned int new_size) {
	void *p = realloc(ptr, new_size);
	if (p != NULL && new_size > old_size) {
		memset((unsigned int *)p + old_size, 0, new_size - old_size);
	}
	return p;
}


void _mem_free(void *ptr) {
	free(ptr);
}

void _trim(char *str) {
	int len = strlen(str);
	int i;
	int done = 0;
	for (i = len - 1; i >= 0; i--) {
		switch (str[i]) {
		case ' ':
		case '\t':
		case '\r':
		case '\n':
			str[i] = '\0';
			break;
		default:
			done = 1;
			break;
		}
		if (done) {
			break;
		}
	}
}

static int _load_credential(SPAS_CREDENTIAL *pcred, char *path) {
	FILE *fp = NULL;
	char buf[SPAS_MAX_KEY_LEN * 2];
	if (pcred == NULL || path == NULL) {
		return ERROR_INVALID_PARAM;
	}
	fp = fopen(path, "r");
	if (fp == NULL) {
		return ERROR_FILE_OPEN;
	}
	memset(pcred, 0, sizeof(SPAS_CREDENTIAL));
	while (fgets(buf, sizeof(buf), fp)) {
		_trim(buf);
		int len = strlen(SPAS_ACCESS_KEY_TAG);
		if (strncmp(buf, SPAS_ACCESS_KEY_TAG, len) == 0 && buf[len] == '=') {
			strncpy(pcred->access_key, buf + len + 1, SPAS_MAX_KEY_LEN - 1);
		}
		else {
			len = strlen(SPAS_SECRET_KEY_TAG);
			if (strncmp(buf, SPAS_SECRET_KEY_TAG, len) == 0 && buf[len] == '=') {
				strncpy(pcred->secret_key, buf + len + 1, SPAS_MAX_KEY_LEN - 1);
			}
		}
	}
	fclose(fp);
	if (strlen(pcred->access_key) == 0 || strlen(pcred->secret_key) == 0) {
		return ERROR_MISSING_KEY;
	}
	return NO_ERROR;
}

#ifndef WIN32
static void _reload_credential(int sig) {
	int ret;
	SPAS_CREDENTIAL credential;
	struct stat status;
	struct sigaction act;
	
	if (sig != SIGALRM) {
		return;
	}
	
	memset(&act, 0, sizeof(act));
	act.sa_handler = _reload_credential;
	sigaction(SIGALRM, &act, NULL);
	alarm(refresh);
	if (g_path[0] != '\0') {
		ret = stat(g_path, &status);
		if (ret != 0) {
			return;
		}
		if (status.st_mtime == modified) {
			return;
		}
		ret = _load_credential(&credential, g_path);
		if (ret != NO_ERROR) {
			return;
		}
#ifdef SPAS_MT
		pthread_mutex_lock(&cred_mutex);
#endif
		memcpy(&g_credential, &credential, sizeof(SPAS_CREDENTIAL));
#ifdef SPAS_MT
		pthread_mutex_unlock(&cred_mutex);
#endif
		modified = status.st_mtime;
	}
}

static int _update_credential_by_alarm() {
	struct sigaction act;
	
	memset(&act, 0, sizeof(act));
	act.sa_handler = _reload_credential;
	sigaction(SIGALRM, &act, NULL);
	alarm(refresh);
	return NO_ERROR;
}
#endif 

#ifdef SPAS_MT

static void * _update_credential_entry(void *arg) {
	int ret;
	SPAS_CREDENTIAL credential;
	struct stat status;
	struct timeval tv;
	while (1) {
		tv.tv_sec = refresh;
		tv.tv_usec = 0;
		select(0, NULL, NULL, NULL, &tv);
		if (g_path[0] != '\0') {
			ret = stat(g_path, &status);
			if (ret != 0) {
				continue;
			}
			if (status.st_mtime == modified) {
				continue;
			}
			ret = _load_credential(&credential, g_path);
			if (ret != NO_ERROR) {
				continue;
			}
			pthread_mutex_lock(&cred_mutex);
			memcpy(&g_credential, &credential, sizeof(SPAS_CREDENTIAL));
			pthread_mutex_unlock(&cred_mutex);
			modified = status.st_mtime;
		}
	}
	return NULL;
}

static int _update_credential_by_thread() {
	pthread_t tid;
	int ret;

	ret = pthread_create(&tid, NULL, _update_credential_entry, NULL);
	if (ret != 0) {
		return ERROR_UPDATE_CREDENTIAL;
	}
	pthread_detach(tid);
	return NO_ERROR;
}



int spas_load_credential(char *path, CREDENTIAL_UPDATE_MODE mode) {
	int ret = NO_ERROR;
	SPAS_CREDENTIAL credential;
	
	if (g_loaded) {
		return NO_ERROR;
	}
	if (path == NULL) {
		path = getenv(SPAS_CREDENTIAL_ENV);
		if (path == NULL) {
			return ERROR_NO_CREDENTIAL;
		}
	}
	strncpy(g_path, path, SPAS_MAX_PATH - 1);
	ret = _load_credential(&credential, path);
	if (ret != NO_ERROR) {
		return ret;
	}
#ifdef SPAS_MT
	pthread_mutex_lock(&cred_mutex);
#endif
	if (!g_loaded) {
		memcpy(&g_credential, &credential, sizeof(SPAS_CREDENTIAL));
		g_loaded = 1;
	}
#ifdef SPAS_MT
	pthread_mutex_unlock(&cred_mutex);
#endif
	switch (mode) {
	case UPDATE_BY_ALARM:
		ret = _update_credential_by_alarm();
		break;
#ifdef SPAS_MT
	case UPDATE_BY_THREAD:
		ret = _update_credential_by_thread();
		break;
#endif
	case NO_UPDATE:
	default:
		ret = NO_ERROR;
		break;
	}
	return ret;
}

#endif

SPAS_CREDENTIAL * spas_get_credential(void) {
	SPAS_CREDENTIAL *credential = (SPAS_CREDENTIAL *)_mem_alloc(sizeof(SPAS_CREDENTIAL));
	if (credential != NULL) {
#ifdef SPAS_MT
		pthread_mutex_lock(&cred_mutex);
#endif
		memcpy(credential, &g_credential, sizeof(SPAS_CREDENTIAL));
#ifdef SPAS_MT
		pthread_mutex_unlock(&cred_mutex);
#endif
	}
	return credential;
}


int spas_set_access_key(char *key) {
	int len = 0;
	if (key == NULL) {
		return ERROR_INVALID_PARAM;
	}
	len = strlen(key);
	if (len == 0 || len >= SPAS_MAX_KEY_LEN) {
		return ERROR_KEY_LENGTH;
	}
#ifdef SPAS_MT
	pthread_mutex_lock(&cred_mutex);
#endif
	memcpy(g_credential.access_key, key, len + 1);
#ifdef SPAS_MT
	pthread_mutex_unlock(&cred_mutex);
#endif
	return NO_ERROR;
}

int spas_set_secret_key(char *key) {
	int len = 0;
	if (key == NULL) {
		return ERROR_INVALID_PARAM;
	}
	len = strlen(key);
	if (len == 0 || len >= SPAS_MAX_KEY_LEN) {
		return ERROR_KEY_LENGTH;
	}
#ifdef SPAS_MT
	pthread_mutex_lock(&cred_mutex);
#endif
	memcpy(g_credential.secret_key, key, len + 1);
#ifdef SPAS_MT
	pthread_mutex_unlock(&cred_mutex);
#endif
	return NO_ERROR;
}

char * spas_get_access_key() {
	return g_credential.access_key;
}

char * spas_get_secret_key() {
	return g_credential.secret_key;
}

#ifdef SPAS_MT

static void _free_thread_credential(void* credential)
{
	if (credential != NULL) {
		_mem_free(credential);
	}
}

static void _init_credential_key(void) {
	pthread_key_create(&cred_key, _free_thread_credential);
}

static SPAS_CREDENTIAL * _get_thread_credential(void) {
	int ret = 0;
	SPAS_CREDENTIAL *credential = NULL;
	ret = pthread_once(&cred_once, _init_credential_key);
	if (ret != 0) {
		return NULL;
	}
	credential = pthread_getspecific(cred_key);
	if (credential == NULL) {
		credential = _mem_alloc(sizeof(SPAS_CREDENTIAL));
		if (credential == NULL) {
			return NULL;
		}
		ret = pthread_setspecific(cred_key, credential);
		if (ret != 0) {
			_mem_free(credential);
			return NULL;
		}
	}
	return credential;
}

int spas_load_thread_credential(char *path) {
	int ret = NO_ERROR;
	SPAS_CREDENTIAL * credential = NULL;
	credential = _get_thread_credential();
	if (credential == NULL) {
		return ERROR_MEM_ALLOC;
	}
	ret = _load_credential(credential, path);
	if (ret != NO_ERROR) {
		memset(credential, 0, sizeof(SPAS_CREDENTIAL));
		return ret;
	}
	return NO_ERROR;
}

int spas_set_thread_access_key(char *key) {
	int len = 0;
	SPAS_CREDENTIAL * credential = NULL;
	if (key == NULL) {
		return ERROR_INVALID_PARAM;
	}
	len = strlen(key);
	if (len == 0 || len >= SPAS_MAX_KEY_LEN) {
		return ERROR_KEY_LENGTH;
	}
	credential = _get_thread_credential();
	if (credential == NULL) {
		return ERROR_MEM_ALLOC;
	}
	memcpy(credential->access_key, key, len + 1);
	return NO_ERROR;
}

int spas_set_thread_secret_key(char *key) {
	int len = 0;
	SPAS_CREDENTIAL * credential = NULL;
	if (key == NULL) {
		return ERROR_INVALID_PARAM;
	}
	len = strlen(key);
	if (len == 0 || len >= SPAS_MAX_KEY_LEN) {
		return ERROR_KEY_LENGTH;
	}
	credential = _get_thread_credential();
	if (credential == NULL) {
		return ERROR_MEM_ALLOC;
	}
	memcpy(credential->secret_key, key, len + 1);
	return NO_ERROR;

}

char * spas_get_thread_access_key(void) {
	SPAS_CREDENTIAL * credential = _get_thread_credential();
	if (credential == NULL) {
		return NULL;
	}
	return credential->access_key;
}

char * spas_get_thread_secret_key(void) {
	SPAS_CREDENTIAL * credential = _get_thread_credential();
	if (credential == NULL) {
		return NULL;
	}
	return credential->secret_key;
}

#endif


char * spas_get_signature(const SPAS_PARAM_LIST *list, const char *key) {
	return spas_get_signature2(list, key, SIGN_HMACSHA1);
}

char * spas_get_signature2(const SPAS_PARAM_LIST *list, const char *key, SPAS_SIGN_ALGORITHM algorithm) {
	char *sign = NULL;
	char *data = NULL;
	if (list == NULL || key == NULL) {
		return NULL;
	}
	data = param_list_to_str(list);
	if (data == NULL) {
		return NULL;
	}
	sign = spas_sign2(data, strlen(data),key, algorithm);
	_mem_free(data);
	return sign;
}

char * spas_sign(const char *data, size_t size, const char *key) {
	return spas_sign2(data, size, key, SIGN_HMACSHA1);
}

char * spas_sign2(const char *data, size_t size, const char *key, SPAS_SIGN_ALGORITHM algorithm) {
	int ret;
	int dsize = 0;
	char *sha_buf = NULL;
	char *base64_ret = NULL;
	if (data == NULL || key == NULL) {
		return NULL;
	}
	if (algorithm == SIGN_HMACSHA1) {
		dsize = SHA1_DIGEST_SIZE;
		sha_buf = (char *)_mem_alloc(dsize + 1);
		if (sha_buf == NULL) {
			return NULL;
		}
		ret = hmac_sha1(key, strlen(key), data, size, sha_buf);
		if (ret < 0) {
			_mem_free(sha_buf);
			return NULL;
		}
	}
	else if (algorithm == SIGN_HMACSHA256) {
		dsize = SHA256_DIGEST_SIZE;
		sha_buf = (char *)_mem_alloc(dsize + 1);
		if (sha_buf == NULL) {
			return NULL;
		}
		ret = hmac_sha256(key, strlen(key), data, strlen(data), sha_buf);
		if (ret < 0) {
			_mem_free(sha_buf);
			return NULL;
		}
	}
	else {
		return NULL;
	}
	ret = base64_encode_alloc(sha_buf, dsize, &base64_ret);
	_mem_free(sha_buf);
	return base64_ret;
	
}

void    spas_mem_free(char *pSignature)
{
	_mem_free(pSignature);
}

char * spas_get_version(void) {
	return SPAS_VERSION;
}

#ifdef __cplusplus
}
#endif